/*
 * alfcrypto is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alfcrypto is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
 */
package com.fegor.alfresco.security.crypto;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;

import org.apache.log4j.Logger;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

public class Crypto {
	private final Logger logger = Logger.getLogger(Crypto.class);

	String password = null;
	public final static int SALT_LEN = 8;
	byte[] vector_init = null;
	byte[] salt_pos = null;

	byte[] input;
	byte[] output;

	Cipher eCipher = null;
	Cipher deCipher = null;

	private final int KEYLEN_BITS = 128;
	private final int ITERATIONS = 65536;

	/**
	 * Constructor
	 */
	public Crypto() {
	}

	/**
	 * Encryption configuration
	 * 
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws NoSuchPaddingException
	 * @throws InvalidParameterSpecException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws UnsupportedEncodingException
	 * @throws InvalidKeyException
	 */
	public void configEncrypt() throws NoSuchAlgorithmException,
			InvalidKeySpecException, NoSuchPaddingException,
			InvalidParameterSpecException, IllegalBlockSizeException,
			BadPaddingException, UnsupportedEncodingException,
			InvalidKeyException {
		SecretKeyFactory factory = null;
		SecretKey tmp = null;

		salt_pos = new byte[SALT_LEN];
		SecureRandom rnd = new SecureRandom();
		rnd.nextBytes(salt_pos);

		if (logger.isDebugEnabled())
			logger.debug(this.getClass().getName() + ": [salt: "
					+ (new String(Hex.encodeHex(salt_pos))) + "]");

		factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");

		/*
		 * http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
		 * .shtml
		 */
		KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
				ITERATIONS, KEYLEN_BITS);
		tmp = factory.generateSecret(spec);
		SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");

		eCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		eCipher.init(Cipher.ENCRYPT_MODE, secret);
		AlgorithmParameters params = eCipher.getParameters();

		vector_init = params.getParameterSpec(IvParameterSpec.class).getIV();

		if (logger.isDebugEnabled())
			logger.debug(this.getClass().getName() + ": [vector ini: "
					+ (new String(Hex.encodeHex(vector_init))) + "]");
	}

	/**
	 * Decryption configuration
	 * 
	 * @param initvec
	 * @param salt
	 * @throws NoSuchAlgorithmException
	 * @throws InvalidKeySpecException
	 * @throws NoSuchPaddingException
	 * @throws InvalidKeyException
	 * @throws InvalidAlgorithmParameterException
	 * @throws DecoderException
	 */
	public void configDecrypt(String initvec, String salt)
			throws NoSuchAlgorithmException, InvalidKeySpecException,
			NoSuchPaddingException, InvalidKeyException,
			InvalidAlgorithmParameterException, DecoderException {
		SecretKeyFactory factory = null;
		SecretKey tmp = null;
		SecretKey secret = null;

		salt_pos = Hex.decodeHex(salt.toCharArray());

		if (logger.isDebugEnabled())
			logger.debug(this.getClass().getName() + ": [salt: "
					+ (new String(Hex.encodeHex(salt_pos))) + "]");

		vector_init = Hex.decodeHex(initvec.toCharArray());
		if (logger.isDebugEnabled())
			logger.debug(this.getClass().getName() + ": [vector ini: "
					+ (new String(Hex.encodeHex(vector_init))) + "]");

		/*
		 * http://www.javamex.com/tutorials/cryptography/unrestricted_policy_files
		 * .shtml
		 */
		factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
		KeySpec spec = new PBEKeySpec(password.toCharArray(), salt_pos,
				ITERATIONS, KEYLEN_BITS);

		tmp = factory.generateSecret(spec);
		secret = new SecretKeySpec(tmp.getEncoded(), "AES");

		deCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
		deCipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(
				vector_init));
	}

	/**
	 * Cipher input
	 * 
	 * @param input
	 *            - the cleartext file to be encrypted
	 * @param output
	 *            - the encrypted data file
	 * @throws IOException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws ShortBufferException
	 */
	public void Cipher() throws IOException, IllegalBlockSizeException,
			BadPaddingException, ShortBufferException {
		try {
			this.output = eCipher.doFinal(this.input);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Decipher input
	 * 
	 * @param input
	 *            - the cleartext file to be encrypted
	 * @param output
	 *            - the encrypted data file
	 * @throws IOException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 * @throws ShortBufferException
	 */
	public void Decipher() throws IOException, IllegalBlockSizeException,
			BadPaddingException, ShortBufferException {
		try {
			this.output = deCipher.doFinal(this.input);
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
	}

	/*
	 * Methods setter and getter
	 */
	public void setInput(byte[] input) {
		this.input = input;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getSalt() {
		return (new String(Hex.encodeHex(salt_pos)));
	}

	public String getVectorInit() {
		return (new String(Hex.encodeHex(vector_init)));
	}

	public byte[] getOutput() {
		return this.output;
	}
}
